﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace SharpShare.Utilities {
    public class RelinquishableStream : Stream {
        private Timer _disposeStreamTimer;
        private object _lock = new object();
        private int _retainCount;
        private long _lastPosition;

        public RelinquishableStream(Func<Stream> streamCreator) {
            this.StreamCreator = streamCreator;
            this.RelinquishTimeout = TimeSpan.FromSeconds(10.0);
            _disposeStreamTimer = new Timer(DisposeStreamTimerFired, null, Timeout.Infinite, Timeout.Infinite);
        }

        public Func<Stream> StreamCreator { get; private set; }

        public TimeSpan RelinquishTimeout { get; set; }

        public override bool CanRead {
            get {
                using (Stream stream = AcquireStream()) {
                    return stream.CanRead;
                }
            }
        }

        public override bool CanSeek {
            get {
                using (Stream stream = AcquireStream()) {
                    return stream.CanSeek;
                }
            }
        }

        public override bool CanWrite {
            get {
                using (Stream stream = AcquireStream()) {
                    return stream.CanWrite;
                }
            }
        }

        public override void Flush() {
            using (Stream stream = AcquireStream()) {
                stream.Flush();
            }
        }

        public override long Length {
            get {
                using (Stream stream = AcquireStream()) {
                    return stream.Length;
                }
            }
        }

        public override long Position {
            get {
                using (Stream stream = AcquireStream()) {
                    return stream.Position;
                }
            }
            set {
                using (Stream stream = AcquireStream()) {
                    stream.Position = value;
                }
            }
        }

        public override int Read(byte[] buffer, int offset, int count) {
            using (Stream stream = AcquireStream()) {
                return stream.Read(buffer, offset, count);
            }
        }

        public override long Seek(long offset, SeekOrigin origin) {
            using (Stream stream = AcquireStream()) {
                return stream.Seek(offset, origin);
            }
        }

        public override void SetLength(long value) {
            using (Stream stream = AcquireStream()) {
                stream.SetLength(value);
            }
        }

        public override void Write(byte[] buffer, int offset, int count) {
            using (Stream stream = AcquireStream()) {
                stream.Write(buffer, offset, count);
            }
        }

        private Stream CurrentBaseStream { get; set; }

        private Stream AcquireStream() {
            lock (_lock) {
                if (++_retainCount == 1 && this.CurrentBaseStream == null) {
                    Stream baseStream = this.StreamCreator();

                    if (baseStream == null) {
                        throw new InvalidOperationException("Stream creator did not return a stream.");
                    }

                    baseStream.Position = _lastPosition;

                    this.CurrentBaseStream = baseStream;
                }

                _disposeStreamTimer.Change(Timeout.Infinite, Timeout.Infinite);

                AcquiredStream newStream = new AcquiredStream(this, this.CurrentBaseStream);

                return newStream;
            }
        }

        private void ReleaseStream() {
            lock (_lock) {
                if (--_retainCount == 0) {
                    _disposeStreamTimer.Change(this.RelinquishTimeout, TimeSpan.FromMilliseconds(-1));
                }
            }
        }

        private void DisposeStreamTimerFired(object state) {
            lock (_lock) {
                if (_retainCount != 0) {
                    return;
                }

                Stream oldStream = this.CurrentBaseStream;
                this.CurrentBaseStream = null;

                _lastPosition = oldStream.Position;

                oldStream.Dispose();
            }
        }

        private sealed class AcquiredStream : Stream {
            public AcquiredStream(RelinquishableStream ownerStream, Stream baseStream) {
                this.OwnerStream = ownerStream;
                this.BaseStream = baseStream;
            }

            public RelinquishableStream OwnerStream { get; private set; }
            public Stream BaseStream { get; private set; }

            public override bool CanRead {
                get { return this.BaseStream.CanRead; }
            }

            public override bool CanSeek {
                get { return this.BaseStream.CanSeek; }
            }

            public override bool CanWrite {
                get { return this.BaseStream.CanWrite; }
            }

            public override void Flush() {
                lock (this.BaseStream) {
                    this.BaseStream.Flush();
                }
            }

            public override long Length {
                get { return this.BaseStream.Length; }
            }

            public override long Position {
                get {
                   return  this.BaseStream.Position;
                }
                set {
                    this.BaseStream.Position = value;
                }
            }

            public override int Read(byte[] buffer, int offset, int count) {
                return this.BaseStream.Read(buffer, offset, count);
            }

            public override long Seek(long offset, SeekOrigin origin) {
                return this.BaseStream.Seek(offset, origin);
            }

            public override void SetLength(long value) {
                this.BaseStream.SetLength(value);
            }

            public override void Write(byte[] buffer, int offset, int count) {
                this.BaseStream.Write(buffer, offset, count);
            }

            protected override void Dispose(bool disposing) {
                base.Dispose(disposing);

                this.OwnerStream.ReleaseStream();
            }
        }

    }
}
